{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.120567Z",
     "start_time": "2025-01-17T11:09:46.099165Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 准备数据\n",
    "对非数据集数据进行处理，将其转换为张量。\n"
   ],
   "id": "b2ca68305d4f1003"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.182760Z",
     "start_time": "2025-01-17T11:09:46.161577Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home=\"./data\")\n",
    "\n",
    "print(type(housing))  # <class'sklearn.utils.Bunch'>\n",
    "print(\"-\" * 50)\n",
    "\n",
    "print(housing.data.shape)  # (20640, 8)\n",
    "print(\"-\" * 50)\n",
    "print(type(housing.data))  # <class 'numpy.ndarray'>\n",
    "print(\"-\" * 50)\n",
    "\n",
    "print(housing.target.shape)  # (20640,)\n",
    "print(\"-\" * 50)\n",
    "print(type(housing.target))  # <class 'numpy.ndarray'>"
   ],
   "id": "179320fc991c0d9e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'sklearn.utils._bunch.Bunch'>\n",
      "--------------------------------------------------\n",
      "(20640, 8)\n",
      "--------------------------------------------------\n",
      "<class 'numpy.ndarray'>\n",
      "--------------------------------------------------\n",
      "(20640,)\n",
      "--------------------------------------------------\n",
      "<class 'numpy.ndarray'>\n"
     ]
    }
   ],
   "execution_count": 21
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.200636Z",
     "start_time": "2025-01-17T11:09:46.183762Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# 将数据集分为训练集和测试集\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state=7, test_size=0.25)\n",
    "\n",
    "# 将训练集分为训练集和验证集\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state=11)\n",
    "\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "# 把3个数据集都放到字典中\n",
    "dataset_maps = {\n",
    "    \"train\": (x_train, y_train),  # 训练集\n",
    "    \"valid\": (x_valid, y_valid),  # 验证集\n",
    "    \"test\": (x_test, y_test)  # 测试集\n",
    "}\n"
   ],
   "id": "6438d1e42b76dee3",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.215839Z",
     "start_time": "2025-01-17T11:09:46.201640Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "scaler = StandardScaler()  #标准化\n",
    "\n",
    "# fit和fit_transform的区别，\n",
    "# fit是计算均值和方差，fit_transform是先fit，然后transform\n",
    "scaler.fit(x_train)"
   ],
   "id": "31716505cd2977d0",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-2 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-2 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-2 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-2 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-2 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-2 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-2 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-2 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-2 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-2 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-2\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-2\" type=\"checkbox\" checked><label for=\"sk-estimator-id-2\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 23
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 构建数据集",
   "id": "16087ce2514248b7"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.252363Z",
     "start_time": "2025-01-17T11:09:46.241851Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#构建私有数据集，就需要用如下方式\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "\n",
    "# 因为最终目的是为了将数据分批，所以要继承自Dataset\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, dataset_maps, mode=\"train\"):\n",
    "        # x,y都是ndarray类型\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        # from_numpy将NumPy数组转换成PyTorch张量\n",
    "        # transform 方法会对 self.x 进行缩放，使其符合指定的分布（如均值为 0，标准差为 1，或者在某个范围内）\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        # 处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "        self.y = torch.from_numpy(self.y.reshape(-1, 1)).float()\n",
    "        # 处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "\n",
    "    def __len__(self):  # 必须写\n",
    "        return len(self.x)  # 返回数据集的长度，0维的size\n",
    "\n",
    "    def __getitem__(self, index):  # 必须写\n",
    "        # idx是索引，返回的是数据和标签，这里是一个样本\n",
    "        return self.x[index], self.y[index]\n",
    "        # 返回第index个数据，0维的index\n",
    "\n",
    "\n",
    "#train_ds是dataset类型的数据，与前面例子的FashionMNIST类型一致\n",
    "train_ds = HousingDataset(dataset_maps, mode=\"train\")\n",
    "valid_ds = HousingDataset(dataset_maps, mode=\"valid\")\n",
    "test_ds = HousingDataset(dataset_maps, mode=\"test\")"
   ],
   "id": "6983b17b0a43ddbf",
   "outputs": [],
   "execution_count": 24
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.302375Z",
     "start_time": "2025-01-17T11:09:46.285376Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0]  # 第一个样本的x和y，都是张量",
   "id": "32eb5ab0d2d15cec",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824]),\n",
       " tensor([3.2260]))"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 25
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.338758Z",
     "start_time": "2025-01-17T11:09:46.332385Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][0]",
   "id": "5b70d99fe2daee9e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824])"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 26
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.361724Z",
     "start_time": "2025-01-17T11:09:46.355765Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][1]",
   "id": "2cac8b93a241b2cc",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([3.2260])"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 27
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## DataLoader",
   "id": "ac48caac2c0b7d1b"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.380537Z",
     "start_time": "2025-01-17T11:09:46.373734Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "#放到DataLoader中的train_ds, valid_ds, test_ds都是dataset类型的数据\n",
    "batch_size = 32  #batch_size是可以调的超参数\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "e4913bc7597b3d04",
   "outputs": [],
   "execution_count": 28
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "d35e08caf2939a19"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.413026Z",
     "start_time": "2025-01-17T11:09:46.406545Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 1)\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        # 因为输入的就已经是一维的张量，所以不需要展平\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        return logits\n"
   ],
   "id": "95800ab9c21dfe0e",
   "outputs": [],
   "execution_count": 29
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.429598Z",
     "start_time": "2025-01-17T11:09:46.423030Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 早停\n",
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ],
   "id": "dafde4c7bc482222",
   "outputs": [],
   "execution_count": 30
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.444304Z",
     "start_time": "2025-01-17T11:09:46.438613Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "# 评估函数\n",
    "# 因为是回归问题，所以计算准确率无意义\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "    return np.mean(loss_list)  # # 返回验证集平均损失和准确率"
   ],
   "id": "fee145430e7ea5df",
   "outputs": [],
   "execution_count": 31
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:09:46.471017Z",
     "start_time": "2025-01-17T11:09:46.463313Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练函数\n",
    "def training(model,\n",
    "             train_loader,\n",
    "             test_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss = evaluate(model, test_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ],
   "id": "3d36c3eaec6fc763",
   "outputs": [],
   "execution_count": 32
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:11:04.688390Z",
     "start_time": "2025-01-17T11:09:46.475027Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失,均方误差\n",
    "loss_fct = nn.MSELoss()  # 损失函数\n",
    "# 2. 定义优化器 采用SGD优化器\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. 定义早停\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=0.001)\n",
    "\n",
    "model = model.to(device)\n",
    "record_dict = training(model,\n",
    "                       train_loader,\n",
    "                       val_loader,\n",
    "                       epoch,\n",
    "                       loss_fct,\n",
    "                       optimizer,\n",
    "                       early_stop_callback=early_stop_callback,\n",
    "                       eval_step=len(train_loader)\n",
    "                       )"
   ],
   "id": "6fb98b8ab19337eb",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/36300 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "9d91ef9660754106be3e3e42b606c7e7"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 33
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": "record_dict[\"train\"][-5:]",
   "id": "acc7cd1cd573870",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.36126938462257385, 'step': 36295},\n",
       " {'loss': 0.3410148620605469, 'step': 36296},\n",
       " {'loss': 0.1409761905670166, 'step': 36297},\n",
       " {'loss': 0.1000152975320816, 'step': 36298},\n",
       " {'loss': 0.16496525704860687, 'step': 36299}]"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 34
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": "record_dict[\"val\"][-5:]",
   "id": "172382f4d0c2e1a8",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.3319742581691624, 'step': 34485},\n",
       " {'loss': 0.32113702724541515, 'step': 34848},\n",
       " {'loss': 0.3213689131431343, 'step': 35211},\n",
       " {'loss': 0.32256392555788527, 'step': 35574},\n",
       " {'loss': 0.3215004051150369, 'step': 35937}]"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 35
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:11:04.890596Z",
     "start_time": "2025-01-17T11:11:04.703475Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record_dict)  #横坐标是 steps"
   ],
   "id": "e2554260e338697e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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uUXOnTuX6zaOHz8uw4YNk4iICKlZs6Y8/fTTkp2dnes6S5culc6dO6vu4qZNm8r06dPFaMuXL5fhw4erldswzrlz5+b6OXqJsXNunTp1JDw8XPr37y8HDhzIdZ1Lly7J6NGj1WIx0dHRct9990lycnKu62zfvl2uueYa9XrAqnVvvPFGvvvy448/qscZ12nXrp3Mnz9fPDn2MWPG5HsdDB482OfHPmXKFOnWrZtabRevzREjRsi+fftyXcfI17hR7xUlGXffvn3zPecPPfSQT48bpk6dKu3bt3cu6tSzZ0/57bffTP18l3TsfU36nOeimcD333+vhYSEaF9++aW2a9cu7f7779eio6O1c+fOab7gxRdf1Nq0aaOdOXPGeTl//rzz5w899JAWFxenLV68WNu4caN21VVXab169XL+PDs7W2vbtq3Wv39/bcuWLdr8+fO1mJgYbeLEic7rHD58WIuIiNAmTJig7d69W3v//fe1wMBAbcGCBYaOFfft+eef12bPno3ZSNqcOXNy/fz111/XqlSpos2dO1fbtm2bduONN2qNGjXS0tLSnNcZPHiw1qFDB23t2rXaihUrtKZNm2p33nmn8+cJCQlarVq1tNGjR2s7d+7UvvvuOy08PFz75JNPnNdZtWqVGv8bb7yhHo9//vOfWnBwsLZjxw6Pjf2ee+5RY3N9HVy6dCnXdXxx7IMGDdKmTZum7s/WrVu1oUOHavXr19eSk5MNf40b+V5RknFfe+216j64Pud4Dn153PDzzz9rv/76q7Z//35t37592nPPPadeY3gszPp8l3Ts15r0OXdlisCie/fu2rhx45xfW61WLTY2VpsyZYrmK4EFDhYFuXLlinpR/vjjj87v7dmzRx2Y1qxZo77GCy8gIEA7e/as8zpTp07VoqKitIyMDPX1P/7xDxW8uLr99tvVm5+n5D242mw2rXbt2tqbb76Za/yhoaHqAAn4I8L/27Bhg/M6v/32m2axWLRTp06prz/66COtatWqzrHDM888o7Vo0cL59W233aYNGzYs1/3p0aOH9uCDD2pGKCywuOmmmwr9P2YZe3x8vBrHsmXLDH+Ne/K9Iu+49YPM448/Xuj/McO4dXhdfv75537zfBc0dn95zn2+FJKZmSmbNm1SKXPX/Ujw9Zo1a8RXIN2PFHnjxo1VqhupMMDYsG2u6/iQwq5f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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 36
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 测试集",
   "id": "d9f6852bfa6d34fe"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T14:17:41.144167Z",
     "start_time": "2025-01-16T14:17:41.086072Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()  # 评估模式\n",
    "loss=evaluate(model, test_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}\")"
   ],
   "id": "506c5db54876b6ed",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.3422\n"
     ]
    }
   ],
   "execution_count": 30
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
